// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: GPL-3.0-or-later

package xyz.apiote.bimba.czwek.api

import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.util.Log
import androidx.core.graphics.drawable.toDrawable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import org.openapitools.client.infrastructure.ServerException
import xyz.apiote.bimba.czwek.R
import xyz.apiote.bimba.czwek.data.exceptions.TransientException
import xyz.apiote.bimba.czwek.data.exceptions.UserException
import xyz.apiote.bimba.czwek.data.settings.SettingsRepository
import xyz.apiote.bimba.czwek.data.sources.Server
import xyz.apiote.bimba.czwek.network.isNetworkAvailable
import xyz.apiote.bimba.czwek.network.mapError
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.time.LocalDate
import java.time.format.DateTimeFormatter

// todo [3.2] constants
// todo [3.2] split api files to classes files

@Serializable
data class TrafficServer(
	val url: String,
	val description: String,
	val seatsRequired: List<String>,
) {
	fun id(): String = URLEncoder.encode(url, "utf-8")
}

@Serializable
data class Traffic(
	val authEndpoint: String,
	val accountEndpoint: String,
	val servers: List<TrafficServer>,
	var selectedServer: Int = 0
)

data class Result(val stream: InputStream?, val error: Error?)

data class Error(val statusCode: Int, val stringResource: Int, val imageResource: Int) :
	Throwable() {
	companion object {
		fun fromTransitous(e: ServerException): Error =
			Error(e.statusCode, R.string.error, R.drawable.error_other)
	}
}

suspend fun getLine(
	context: Context,
	server: Server,
	feedID: String,
	lineName: String,
	lineID: String
): Result {
	return request(
		server,
		"lines",
		lineName,
		mapOf("line" to lineID),
		context,
		arrayOf(1u, 2u, 3u),
		feedID
	)
}

suspend fun getDepartures(
	context: Context,
	server: Server,
	feedID: String,
	stop: String,
	date: LocalDate?,
	limit: Int? = null
): Result {
	val params = mutableMapOf("code" to stop)
	if (date != null) {
		params["date"] = date.format(DateTimeFormatter.ISO_LOCAL_DATE)
	}
	if (limit != null) {
		params["limit"] = limit.toString(10)
	}
	return request(
		server,
		"departures",
		null,
		params,
		context,
		arrayOf(1u, 2u, 3u, 4u),
		feedID
	)
}


suspend fun rawRequest(
	url: URL, context: Context, responseVersion: Array<UInt>
): Result { // TODO InputStream
	return withContext(Dispatchers.IO) {
		val c = (url.openConnection() as HttpURLConnection).apply {
			setRequestProperty(
				"User-Agent",
				"${context.getString(R.string.applicationId)}/${context.getString(R.string.versionName)} (${Build.VERSION.SDK_INT})"
			)
			// TODO plusA put token in authorization
			responseVersion.forEach { addRequestProperty("Accept", "application/$it+bare") }
		}
		try {
			if (c.responseCode == 200) {
				Result(c.inputStream, null)
			} else {
				Log.e("Request", c.inputStream.bufferedReader().use { it.readText() })
				throw mapError(c.responseCode, null)
			}
		} catch (e: IOException) {
			throw TransientException(
				e.message,
				Error(0, R.string.error_connecting, R.drawable.error_server)
			)
		}
	}
}

suspend fun request(
	server: Server,
	resource: String,
	item: String?,
	params: Map<String, String>,
	context: Context,
	responseVersion: Array<UInt>,
	feeds: String?
): Result {
	return withContext(Dispatchers.IO) {
		val selectedFeeds =
			feeds ?: SettingsRepository().getServerFeeds(context)
				.getBimbaSelection()
		if (selectedFeeds.isEmpty()) {
			throw UserException(
				"no feeds selected",
				Error(0, R.string.no_feeds_selected, R.drawable.error_feeds)
			)
		}

		val url = URL( // todo [3.2] scheme, host, path, constructed query
			"${server.getSelectedServer().url}/${selectedFeeds}/$resource${
				if (item == null) {
					""
				} else {
					"/${URLEncoder.encode(item, "utf-8")}"
				}
			}${
				params.map {
					"${it.key}=${
						URLEncoder.encode(
							it.value, "utf-8"
						)
					}"
				}.joinToString("&", "?")
			}"
		)
		rawRequest(url, context, responseVersion)
	}
}

fun hostWithScheme(host: String): String =
	if (host.startsWith("http://") or host.startsWith("https://")) {
		host
	} else {
		"https://$host"
	}


suspend fun getImageFromUrl(context: Context, url: String): BitmapDrawable? {
	if (!isNetworkAvailable(context)) {
		return null
	}
	return withContext(Dispatchers.IO) {
		val c = (URL(url).openConnection() as HttpURLConnection)
		try {
			if (c.responseCode == 200) {
				BitmapFactory.decodeStream(c.inputStream).toDrawable(context.resources)
			} else {
				null
			}
		} catch (_: IOException) {
			null
		}
	}
}